package com.jrx.anytxn.investor.service.impl;

import com.jrx.anytxn.common.constant.CommonContants;
import com.jrx.anytxn.common.data.TxnRespCode;
import com.jrx.anytxn.common.exception.TxnBizException;
import com.jrx.anytxn.common.exception.TxnException;
import com.jrx.anytxn.common.uitl.CollectorsUtils;
import com.jrx.anytxn.common.uitl.DateUtils;
import com.jrx.anytxn.investor.bean.InvestorLoanPlanParamBean;
import com.jrx.anytxn.investor.constant.TransBizConstant;
import com.jrx.anytxn.investor.entity.AmLoanAccountInvestorInfo;
import com.jrx.anytxn.investor.entity.AmLoanMainInvestorInfo;
import com.jrx.anytxn.investor.entity.AmRepaymentPlanInvestorInfo;
import com.jrx.anytxn.investor.mapper.ext.ExtAmRepaymentPlanInvestorInfoMapper;
import com.jrx.anytxn.investor.service.IInvestorRepaymentPlanService;
import com.jrx.anytxn.number.exception.SegmentException;
import com.jrx.anytxn.number.service.ISegmentService;
import com.jrx.anytxn.param.entity.*;
import com.jrx.anytxn.param.service.investor.IPrInvestorInstallmentFeeService;
import com.jrx.anytxn.param.service.investor.IPrInvestorInterestRateService;
import com.jrx.anytxn.param.service.product.IAmortMethodService;
import com.jrx.anytxn.param.service.product.IProductTableService;
import com.jrx.anytxn.transaction.entity.AmRepaymentPlanInfo;
import com.jrx.anytxn.transaction.service.IRepaymentPlanService;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 联合贷还款计划Service接口实现类
 *
 * @author liuyang
 * @date 2020-05-18
 */
@Service
public class InvestorRepaymentPlanServiceImpl implements IInvestorRepaymentPlanService {


    private static final Logger logger = LoggerFactory.getLogger(InvestorRepaymentPlanServiceImpl.class);

    @Autowired
    private IAmortMethodService amortMethodService;

    @Autowired
    private ISegmentService segmentService;

    @Autowired
    private IPrInvestorInstallmentFeeService investorInstallmentFeeService;

    @Autowired
    private IPrInvestorInterestRateService investorInterestRateService;

    @Autowired
    private IRepaymentPlanService repaymentPlanService;

    @Resource
    private ExtAmRepaymentPlanInvestorInfoMapper extAmRepaymentPlanInvestorInfoMapper;

    @Autowired
    private IProductTableService productTableService;


    /**
     * 生成还款计划
     *
     * @param planParamBean           还款计划参数
     * @param loanMainInvestorInfo    贷款信息
     * @param loanAccountInvestorInfo 贷款账户
     * @return List<AmRepaymentPlanInvestorInfo> 还款计划
     * @throws TxnException
     * @author liuyang
     * @date 2020-05-19
     */
    @Override
    public List<AmRepaymentPlanInvestorInfo> buildRepaymentPlanInvestor(InvestorLoanPlanParamBean planParamBean, AmLoanMainInvestorInfo loanMainInvestorInfo, AmLoanAccountInvestorInfo loanAccountInvestorInfo) throws TxnException {

        List<AmRepaymentPlanInvestorInfo> repaymentPlanInvestorInfos = null;

        //获取还款方式
        PrAmortMethod amortMethod = amortMethodService.findByChargerOptionAndId(loanMainInvestorInfo.getAmortMethodTableId(), loanMainInvestorInfo.getOrganizationId(), loanMainInvestorInfo.getTenantId());
        String paymentMethod = amortMethod.getChargeOption();

        // 计算进位条件
        String carryCond = amortMethod.getCarryCond();
        Integer dcimPlace = amortMethod.getDcimPlace();
        Integer carryCondInt = null;
        if (TransBizConstant.CARRY_COND_1.equals(carryCond)) { //四舍五入
            carryCondInt = BigDecimal.ROUND_HALF_UP;
        } else if (TransBizConstant.CARRY_COND_2.equals(carryCond)) {//直接进位
            carryCondInt = BigDecimal.ROUND_UP;
        } else if (TransBizConstant.CARRY_COND_3.equals(carryCond)) {//截位
            carryCondInt = BigDecimal.ROUND_DOWN;
        }

        //计算日、月、年 利率
        Date businessDate = planParamBean.getBusinessDate();
        PrInvestorInterestRate interest = investorInterestRateService.findInterestByInterestId(loanMainInvestorInfo.getInvestorInterestTableId(), loanMainInvestorInfo.getOrganizationId(), loanMainInvestorInfo.getTenantId());
        BigDecimal dailyRate = investorInterestRateService.getDailyRate(interest, businessDate);//日利率
        BigDecimal monthRate = investorInterestRateService.getMonthRate(interest, businessDate);//月利率
        BigDecimal yearRate = investorInterestRateService.getYearRate(interest);//年利率

        // 计算首次还款日
        Date firstPaymentDate = planParamBean.getFirstPaymentDate();
        if (null == planParamBean.getFirstPaymentDate()) {
            firstPaymentDate = computeFirstPaymentDate(businessDate, planParamBean.getPaymentDateDd(), planParamBean.getIntervalDays());
        }

        // 获取手续费参数
        PrInvestorInstallmentFee installmentFee = investorInstallmentFeeService.findInvestorInstallmentByFeeId(loanMainInvestorInfo.getInvestorFeeTableId(), loanMainInvestorInfo.getOrganizationId(), loanMainInvestorInfo.getTenantId());

        String offsetFlag = loanMainInvestorInfo.getOffsetFlag();
        if (TransBizConstant.YES_FLAG.equals(offsetFlag)) {
            // 轧差资金方的还款计划生成
            // 分期付款（等本等息）
            int castTenor = loanAccountInvestorInfo.getCastTenor();
            if (TransBizConstant.PRIN_CHARGE_OPTION_EPEI.equals(paymentMethod)) {
                repaymentPlanInvestorInfos = computeOffsetEPEI(loanMainInvestorInfo, castTenor, dcimPlace, carryCondInt, firstPaymentDate, yearRate, installmentFee, planParamBean.getRepaymentPlanInvestorInfos(), interest, businessDate);
            } else {
                // 非分期付款
                repaymentPlanInvestorInfos = computeOffsetOther(loanMainInvestorInfo, castTenor, dcimPlace, carryCondInt, firstPaymentDate, dailyRate, monthRate, installmentFee, businessDate, planParamBean.getRepaymentPlanInvestorInfos());
            }
        } else {
            // 非轧差资金方的还款计划生成
            // 分期付款（等本等息）
            if (TransBizConstant.PRIN_CHARGE_OPTION_EPEI.equals(paymentMethod)) {
                repaymentPlanInvestorInfos = computeEPEI(loanMainInvestorInfo, dcimPlace, carryCondInt, firstPaymentDate, yearRate, installmentFee, interest, businessDate);
            }
            //等额本息
            else if (TransBizConstant.PRIN_CHARGE_OPTION_EPI.equals(paymentMethod)) {
                repaymentPlanInvestorInfos = computeEPI(loanMainInvestorInfo, dcimPlace, carryCondInt, firstPaymentDate, dailyRate, monthRate, installmentFee, businessDate);
            }
            //等额本金
            else if (TransBizConstant.PRIN_CHARGE_OPTION_EP.equals(paymentMethod)) {
                repaymentPlanInvestorInfos = computeEP(loanMainInvestorInfo, dcimPlace, carryCondInt, firstPaymentDate, dailyRate, monthRate, installmentFee, businessDate);
            }
            //按期付息，到期还本付息
            else if (TransBizConstant.PRIN_CHARGE_OPTION_MONTHLY_INTPAY.equals(paymentMethod)) {
                repaymentPlanInvestorInfos = computeMonthlyInterestPayEnd(loanMainInvestorInfo, dcimPlace, carryCondInt, firstPaymentDate, dailyRate, monthRate, installmentFee, businessDate);
            }
            //按月付息，按年还本付息
            else if (TransBizConstant.PRIN_CHARGE_OPTION_MI_YP.equals(paymentMethod)) {
                repaymentPlanInvestorInfos = computeMonthlyInterestYearPay(loanMainInvestorInfo, dcimPlace, carryCondInt, firstPaymentDate, dailyRate, monthRate, installmentFee, businessDate, loanAccountInvestorInfo.getCastTenor());
            }
            //随见随还 利随本清
            else if (TransBizConstant.PRIN_CHARGE_OPTION_INTPAY.equals(paymentMethod)) {
                repaymentPlanInvestorInfos = computeInterestPayEnd(loanMainInvestorInfo, dcimPlace, carryCondInt, firstPaymentDate, dailyRate, installmentFee);
            }
        }

        return repaymentPlanInvestorInfos;
    }

    /**
     * 分期付款（等本等息）
     *
     * @param loanMainInvestorInfo 贷款信息
     * @param dcimPlace            小数位数
     * @param carryCondInt         进位条件
     * @param firstPaymentDate     首次还款日
     * @param yearRate             年利率
     * @param installmentFee       手续费参数
     * @return List<AmRepaymentPlanInvestorInfo> 还款计划
     * @throws TxnException
     * @author liuyang
     * @date 2020-05-19
     */
    private List<AmRepaymentPlanInvestorInfo> computeEPEI(AmLoanMainInvestorInfo loanMainInvestorInfo, Integer dcimPlace, Integer carryCondInt, Date firstPaymentDate, BigDecimal yearRate, PrInvestorInstallmentFee installmentFee, PrInvestorInterestRate investorInterestRate, Date businessDate) throws TxnException {

        Integer totalTenor = loanMainInvestorInfo.getTotalTenor(); //总期数
        BigDecimal remainAmount = loanMainInvestorInfo.getLoanAmount(); //剩余本金

        //月利率 公式: 年利率 * (1 + 期数) / (24 * 期数)   为准确计算  将除(24 * 期数) 放到了月利率计算公式中
        BigDecimal monthRate =
                yearRate.multiply(BigDecimal.valueOf(1L + totalTenor)).divide(BigDecimal.valueOf(24L * totalTenor), CommonContants.BIGDECIMAL_SCALE_7,
                        BigDecimal.ROUND_HALF_UP);

        //日利率
        int monthBase = investorInterestRateService.getMonthBase(investorInterestRate.getMonthBase(), businessDate);
        BigDecimal dailyRate = monthRate.divide(
                BigDecimal.valueOf(monthBase), CommonContants.BIGDECIMAL_SCALE_7, BigDecimal.ROUND_HALF_UP);

        //月利息 公式: 借款本金 * 月利率
        BigDecimal monthIntr = remainAmount.multiply(monthRate).setScale(dcimPlace, carryCondInt);

        //每期本金 = 贷款金额/总期数
        BigDecimal princial = remainAmount.divide(BigDecimal.valueOf(totalTenor), dcimPlace, carryCondInt);

        List<AmRepaymentPlanInvestorInfo> list = new ArrayList<>();
        for (int i = 1; i <= totalTenor; i++) {

            // 构建还款计划
            AmRepaymentPlanInvestorInfo repaymentPlanInfo = buildRepaymentPlanInvestorInfo(loanMainInvestorInfo);

            // 当期还款日
            Date thisPaymentDueDate = i == 1 ? firstPaymentDate : getCurrentPaymentDueDate(firstPaymentDate, i);

            // 上一期还款日（i是从1开始，所以需要减去2）
            Date lastPaymentDueDate = i == 1 ? businessDate : list.get(i - 2).getPaymentDueDate();

            // 利息本金计算
            BigDecimal interest = getMonthInterest(loanMainInvestorInfo, monthIntr, loanMainInvestorInfo.getLoanAmount(), i, firstPaymentDate, lastPaymentDueDate, thisPaymentDueDate, dailyRate, dcimPlace, carryCondInt);

            //本金扎差 最后一期本金按剩余本经计算
            if (i == totalTenor) {
                princial = remainAmount.setScale(dcimPlace, carryCondInt);
            }

            //计算每月费用
            BigDecimal calcAmnt = BigDecimal.ZERO;
            //按贷款本金计算
            if (TransBizConstant.FEE_CALC_METHOD_1.equals(installmentFee.getChargeBasis())) {
                calcAmnt = loanMainInvestorInfo.getLoanAmount();
            }
            //按每期本金计算
            else if (TransBizConstant.FEE_CALC_METHOD_2.equals(installmentFee.getChargeBasis())) {
                calcAmnt = princial;
            }
            //按剩余本金计算
            else if (TransBizConstant.FEE_CALC_METHOD_0.equals(installmentFee.getChargeBasis())) {
                calcAmnt = remainAmount;
            }
            BigDecimal fee = this.getFeeAmnt(i, calcAmnt, installmentFee, totalTenor, dcimPlace, carryCondInt);

            repaymentPlanInfo.setFeeAmount(fee);//费用
            repaymentPlanInfo.setPrincipalAmount(princial);//本金
            repaymentPlanInfo.setInterestAmount(interest); //利息
            repaymentPlanInfo.setBillingTenor(i);//期数
            repaymentPlanInfo.setTotalAmount(princial.add(interest).add(fee).setScale(dcimPlace, carryCondInt));//还款总金额
            repaymentPlanInfo.setPaymentDueDate(thisPaymentDueDate);//还款日
            repaymentPlanInfo.setIntrStartDate(lastPaymentDueDate);//起息日

            //剩余金额重新计算
            remainAmount = remainAmount.subtract(princial);

            list.add(repaymentPlanInfo);
        }

        return list;
    }

    /**
     * 等额本息计算
     *
     * @param loanMainInvestorInfo 贷款信息
     * @param dcimPlace            小数位数
     * @param carryCondInt         进位条件
     * @param firstPaymentDate     首次还款日
     * @param dailyRate            日利率
     * @param monthRate            月利率
     * @param installmentFee       手续费参数
     * @param businessDate         业务日期
     * @return List<AmRepaymentPlanInvestorInfo> 还款计划
     * @throws TxnBizException
     * @author liuyang
     * @date 2020-05-19
     */
    private List<AmRepaymentPlanInvestorInfo> computeEPI(AmLoanMainInvestorInfo loanMainInvestorInfo, Integer dcimPlace, Integer carryCondInt, Date firstPaymentDate, BigDecimal dailyRate, BigDecimal monthRate, PrInvestorInstallmentFee installmentFee, Date businessDate) throws TxnBizException {

        BigDecimal loanAmount = loanMainInvestorInfo.getLoanAmount();//贷款金额
        Integer totalTenor = loanMainInvestorInfo.getTotalTenor();//总期数

        BigDecimal remainAmount = loanAmount;   //剩余本金
        BigDecimal monthlyPaymentAmount;//月还款金额
        BigDecimal monthlyFee;//月还费用

        // 判断月利率是否是0
        if (BigDecimal.ZERO.compareTo(monthRate) >= 0) {
            // 如果这里的月利率为0，则不代入公式进行计算，月还款金额 = 本金 / 期数
            monthlyPaymentAmount = loanAmount.divide(BigDecimal.valueOf(totalTenor), CommonContants.BIGDECIMAL_SCALE_10, carryCondInt).setScale(dcimPlace, carryCondInt);
        } else {
            //定义平方计算依据
            MathContext mc = new MathContext(TransBizConstant.ZERO, RoundingMode.valueOf(carryCondInt));
            // 公式：月还款金额 = (本金 * 月利率 * ((月利率 + 1)↗期数)) / (((月利率 + 1)↗期数) - 1)
            monthlyPaymentAmount = loanAmount.multiply(monthRate).multiply(monthRate.add(BigDecimal.ONE).pow(totalTenor, mc))
                    .divide(monthRate.add(BigDecimal.ONE).pow(totalTenor, mc).subtract(BigDecimal.ONE), CommonContants.BIGDECIMAL_SCALE_10, carryCondInt).setScale(dcimPlace, carryCondInt);
        }

        //循环期数 生成还款计划
        List<AmRepaymentPlanInvestorInfo> list = new ArrayList<>();
        for (int i = 1; i <= totalTenor; i++) {

            // 构建还款计划
            AmRepaymentPlanInvestorInfo repaymentPlanInfo = buildRepaymentPlanInvestorInfo(loanMainInvestorInfo);

            // 月利息计算
            BigDecimal monthIntr = remainAmount.multiply(monthRate).setScale(dcimPlace, carryCondInt);

            // 当期还款日
            Date thisPaymentDueDate = i == 1 ? firstPaymentDate : getCurrentPaymentDueDate(firstPaymentDate, i);

            // 上一期还款日（i是从1开始，所以需要减去2）
            Date lastPaymentDueDate = i == 1 ? businessDate : list.get(i - 2).getPaymentDueDate();

            // 利息本金计算
            BigDecimal interest = getMonthInterest(loanMainInvestorInfo, monthIntr, remainAmount, i, firstPaymentDate, lastPaymentDueDate, thisPaymentDueDate, dailyRate, dcimPlace, carryCondInt);

            //本金计算
            BigDecimal princial;
            if (i == totalTenor) {
                princial = totalTenor == 1 ? loanAmount : remainAmount.setScale(dcimPlace, carryCondInt);
            } else {
                princial = monthlyPaymentAmount.subtract(interest).setScale(dcimPlace, carryCondInt);
            }

            //计算每月费用
            BigDecimal calcAmnt = BigDecimal.ZERO;
            if (TransBizConstant.FEE_CALC_METHOD_1.equals(installmentFee.getChargeBasis())) {
                calcAmnt = loanAmount;
            } else if (TransBizConstant.FEE_CALC_METHOD_2.equals(installmentFee.getChargeBasis())) {
                calcAmnt = princial;
            } else if (TransBizConstant.FEE_CALC_METHOD_0.equals(installmentFee.getChargeBasis())) {
                calcAmnt = remainAmount;
            }
            monthlyFee = this.getFeeAmnt(i, calcAmnt, installmentFee, totalTenor, dcimPlace, carryCondInt);

            //剩余本金刷新 减去当期本金
            remainAmount = remainAmount.subtract(princial);

            repaymentPlanInfo.setPrincipalAmount(princial);//本金
            repaymentPlanInfo.setInterestAmount(interest);//利息
            repaymentPlanInfo.setBillingTenor(i);//期数
            repaymentPlanInfo.setTotalAmount(princial.add(interest).add(monthlyFee).setScale(dcimPlace, carryCondInt));//当期还款总金额
            repaymentPlanInfo.setFeeAmount(monthlyFee);//费用
            repaymentPlanInfo.setPaymentDueDate(thisPaymentDueDate);//还款日
            repaymentPlanInfo.setIntrStartDate(lastPaymentDueDate);//起息日

            list.add(repaymentPlanInfo);
        }

        return list;
    }

    /**
     * 等额本金
     *
     * @param loanMainInvestorInfo 贷款信息
     * @param dcimPlace            小数位数
     * @param carryCondInt         进位条件
     * @param firstPaymentDate     首次还款日
     * @param dailyRate            日利率
     * @param monthRate            月利率
     * @param installmentFee       手续费参数
     * @param businessDate         业务日期
     * @return List<AmRepaymentPlanInvestorInfo> 还款计划
     * @throws TxnBizException
     * @author liuyang
     * @date 2020-05-19
     */
    private List<AmRepaymentPlanInvestorInfo> computeEP(AmLoanMainInvestorInfo loanMainInvestorInfo, Integer dcimPlace, Integer carryCondInt, Date firstPaymentDate, BigDecimal dailyRate, BigDecimal monthRate, PrInvestorInstallmentFee installmentFee, Date businessDate) throws TxnBizException {

        BigDecimal loanAmount = loanMainInvestorInfo.getLoanAmount(); //贷款金额
        Integer totalTenor = loanMainInvestorInfo.getTotalTenor(); //总期数
        BigDecimal remainAmount = loanAmount; //剩余本金
        BigDecimal monthlyFee;//每月费用

        //每月本金=贷款金额 / 期数
        BigDecimal monthlyPrincial = loanAmount.divide(BigDecimal.valueOf(totalTenor), dcimPlace, carryCondInt);

        //循环期数 生成还款计划
        List<AmRepaymentPlanInvestorInfo> list = new ArrayList<>();
        for (int i = 1; i <= totalTenor; i++) {

            // 构建还款计划
            AmRepaymentPlanInvestorInfo repaymentPlanInfo = buildRepaymentPlanInvestorInfo(loanMainInvestorInfo);

            //月利息
            BigDecimal monthIntr = remainAmount.multiply(monthRate).setScale(dcimPlace, carryCondInt);

            // 当期还款日
            Date thisPaymentDueDate = i == 1 ? firstPaymentDate : getCurrentPaymentDueDate(firstPaymentDate, i);

            // 上一期还款日（i是从1开始，所以需要减去2）
            Date lastPaymentDueDate = i == 1 ? businessDate : list.get(i - 2).getPaymentDueDate();

            // 利息本金计算
            BigDecimal interest = getMonthInterest(loanMainInvestorInfo, monthIntr, remainAmount, i, firstPaymentDate, lastPaymentDueDate, thisPaymentDueDate, dailyRate, dcimPlace, carryCondInt);

            //最后一期（剩余贷款金额）
            if (i == totalTenor) {
                monthlyPrincial = remainAmount.setScale(dcimPlace, carryCondInt);
            }

            //计算每月费用
            BigDecimal calcAmnt = BigDecimal.ZERO;
            if (TransBizConstant.FEE_CALC_METHOD_1.equals(installmentFee.getChargeBasis())) {//按贷款本金
                calcAmnt = loanAmount;
            } else if (TransBizConstant.FEE_CALC_METHOD_2.equals(installmentFee.getChargeBasis())) {//按每期本金
                calcAmnt = monthlyPrincial;
            } else if (TransBizConstant.FEE_CALC_METHOD_0.equals(installmentFee.getChargeBasis())) {//按剩余本金
                calcAmnt = remainAmount;
            }
            monthlyFee = this.getFeeAmnt(i, calcAmnt, installmentFee, totalTenor, dcimPlace, carryCondInt);

            //剩余本金刷新 减掉当期本金
            remainAmount = remainAmount.subtract(monthlyPrincial);

            repaymentPlanInfo.setPrincipalAmount(monthlyPrincial);//本金
            repaymentPlanInfo.setTotalAmount(monthlyPrincial.add(interest).add(monthlyFee).setScale(dcimPlace, carryCondInt));//还款总金额
            repaymentPlanInfo.setInterestAmount(interest);//利息
            repaymentPlanInfo.setFeeAmount(monthlyFee);//费用
            repaymentPlanInfo.setBillingTenor(i);//期数
            repaymentPlanInfo.setPaymentDueDate(thisPaymentDueDate);//还款日
            repaymentPlanInfo.setIntrStartDate(lastPaymentDueDate);//起息日

            list.add(repaymentPlanInfo);
        }
        return list;
    }

    /**
     * 按月付息，到期还本付息
     *
     * @param loanMainInvestorInfo 贷款信息
     * @param dcimPlace            小数位数
     * @param carryCondInt         进位条件
     * @param firstPaymentDate     首次还款日
     * @param dailyRate            日利率
     * @param monthRate            月利率
     * @param installmentFee       手续费参数
     * @param businessDate         业务日期
     * @return List<AmRepaymentPlanInvestorInfo> 还款计划
     * @throws TxnBizException
     * @author liuyang
     * @date 2020-05-19
     */
    private List<AmRepaymentPlanInvestorInfo> computeMonthlyInterestPayEnd(AmLoanMainInvestorInfo loanMainInvestorInfo, Integer dcimPlace, Integer carryCondInt, Date firstPaymentDate, BigDecimal dailyRate, BigDecimal monthRate, PrInvestorInstallmentFee installmentFee, Date businessDate) throws TxnBizException {

        BigDecimal loanAmount = loanMainInvestorInfo.getLoanAmount();//贷款总金额
        Integer totalTenor = loanMainInvestorInfo.getTotalTenor(); //总期数
        BigDecimal monthlyFee;//月费用

        //月利息
        BigDecimal monthIntr = loanAmount.multiply(monthRate).setScale(dcimPlace, carryCondInt);

        //循环期数 生成还款计划
        List<AmRepaymentPlanInvestorInfo> list = new ArrayList<>();
        for (int i = 1; i <= totalTenor; i++) {

            // 构建还款计划
            AmRepaymentPlanInvestorInfo repaymentPlanInfo = buildRepaymentPlanInvestorInfo(loanMainInvestorInfo);

            // 当期还款日
            Date thisPaymentDueDate = i == 1 ? firstPaymentDate : getCurrentPaymentDueDate(firstPaymentDate, i);

            // 上一期还款日（i是从1开始，所以需要减去2）
            Date lastPaymentDueDate = i == 1 ? businessDate : list.get(i - 2).getPaymentDueDate();

            // 利息本金计算
            BigDecimal interest = getMonthInterest(loanMainInvestorInfo, monthIntr, loanAmount, i, firstPaymentDate, lastPaymentDueDate, thisPaymentDueDate, dailyRate, dcimPlace, carryCondInt);

            //计算每月费用
            BigDecimal calcAmnt = BigDecimal.ZERO;
            if (TransBizConstant.FEE_CALC_METHOD_1.equals(installmentFee.getChargeBasis())) {//费用收取方式是按贷款本金计算
                calcAmnt = loanAmount;
            } else if (TransBizConstant.FEE_CALC_METHOD_2.equals(installmentFee.getChargeBasis())) {//费用收取方式是按每期本金计算
                if (i == totalTenor) {
                    calcAmnt = loanAmount;
                }
            } else if (TransBizConstant.FEE_CALC_METHOD_0.equals(installmentFee.getChargeBasis())) {//费用收取方式是按剩余本经
                calcAmnt = loanAmount;
            }
            monthlyFee = this.getFeeAmnt(i, calcAmnt, installmentFee, totalTenor, dcimPlace, carryCondInt);

            // 计算本金和还款总金额
            if (i == totalTenor) {//尾期（尾期中需要还本金）
                repaymentPlanInfo.setTotalAmount(loanAmount.add(interest).add(monthlyFee).setScale(dcimPlace, carryCondInt));//总金额
                repaymentPlanInfo.setPrincipalAmount(loanAmount);//本金
            } else {
                repaymentPlanInfo.setTotalAmount(interest.add(monthlyFee).setScale(dcimPlace, carryCondInt));//总金额
                repaymentPlanInfo.setPrincipalAmount(BigDecimal.ZERO);//本金
            }
            repaymentPlanInfo.setInterestAmount(interest);//利息
            repaymentPlanInfo.setFeeAmount(monthlyFee);//费用
            repaymentPlanInfo.setBillingTenor(i);//期数
            repaymentPlanInfo.setPaymentDueDate(thisPaymentDueDate);//还款日期
            repaymentPlanInfo.setIntrStartDate(lastPaymentDueDate);//起息日

            list.add(repaymentPlanInfo);
        }

        return list;
    }

    /**
     * 按月付息，按年还本
     *
     * @param loanMainInvestorInfo 贷款信息
     * @param dcimPlace            小数位数
     * @param carryCondInt         进位条件
     * @param firstPaymentDate     首次还款日
     * @param dailyRate            日利率
     * @param monthRate            月利率
     * @param installmentFee       手续费参数
     * @param businessDate         业务日期
     * @param castTenor            已抛期数
     * @return List<AmRepaymentPlanInvestorInfo> 还款计划
     * @throws TxnBizException
     * @author liuyang
     * @date 2020-05-19
     */
    private List<AmRepaymentPlanInvestorInfo> computeMonthlyInterestYearPay(AmLoanMainInvestorInfo loanMainInvestorInfo, Integer dcimPlace, Integer carryCondInt, Date firstPaymentDate, BigDecimal dailyRate, BigDecimal monthRate, PrInvestorInstallmentFee installmentFee, Date businessDate, Integer castTenor) throws TxnBizException {

        BigDecimal loanAmount = loanMainInvestorInfo.getLoanAmount();//贷款金额
        Integer totalTenor = loanMainInvestorInfo.getTotalTenor();  //剩余期数
        int calTenor = 0; //参与计算的期数
        if (castTenor != null && castTenor.compareTo(0) > 0) {
            calTenor = castTenor - 1;
        }
        BigDecimal remainAmount = loanAmount; //剩余本金
        BigDecimal monthlyFee;//月费用

        //年还款本金
        BigDecimal yearPrincial = loanAmount.divide(new BigDecimal(totalTenor % CommonContants.MONTH == 0 ? totalTenor / CommonContants.MONTH : totalTenor / CommonContants.MONTH + 1), dcimPlace, carryCondInt);

        //月利息
        BigDecimal monthIntr = loanAmount.multiply(monthRate).setScale(dcimPlace, carryCondInt);

        //循环期数 生成还款计划
        List<AmRepaymentPlanInvestorInfo> list = new ArrayList<>();
        for (int i = 1; i <= totalTenor; i++) {

            // 构建还款计划
            AmRepaymentPlanInvestorInfo repaymentPlanInfo = buildRepaymentPlanInvestorInfo(loanMainInvestorInfo);

            // 当期还款日
            Date thisPaymentDueDate = i == 1 ? firstPaymentDate : getCurrentPaymentDueDate(firstPaymentDate, i);

            // 上一期还款日（i是从1开始，所以需要减去2）
            Date lastPaymentDueDate = i == 1 ? businessDate : list.get(i - 2).getPaymentDueDate();

            // 利息本金计算
            BigDecimal interest = getMonthInterest(loanMainInvestorInfo, monthIntr, remainAmount, i, firstPaymentDate, lastPaymentDueDate, thisPaymentDueDate, dailyRate, dcimPlace, carryCondInt);


            //计算本金和利息
            if (i == 1) {//首期（由于首期中天数存在跨月的情况，分离开来）
                if (i == totalTenor) {//只贷一期
                    logger.debug("第一期处理逻辑，最后一期，还款加入本金金额:{}", remainAmount);
                    repaymentPlanInfo.setPrincipalAmount(remainAmount);//本金
                } else if ((i + calTenor) % CommonContants.MONTH == 0) {//如果本期是第一期，除以12余数为零，需要设置剩余本金
                    logger.debug("第一期处理逻辑，12期整数倍，不是最后一期，还款加入本金金额:{}", yearPrincial);
                    repaymentPlanInfo.setPrincipalAmount(yearPrincial);//本金
                } else {//其他情况
                    logger.debug("第一期处理逻辑，还款不加入本金");
                    repaymentPlanInfo.setPrincipalAmount(BigDecimal.ZERO);//本金
                }
            } else {
                if ((i + calTenor) % CommonContants.MONTH == 0 && i != totalTenor) {//十二个月的整数倍（此时需要还本金，分离开来）
                    repaymentPlanInfo.setPrincipalAmount(yearPrincial);//本金
                } else if (i == totalTenor) {//尾期（此时需要还本金，分离开来）
                    repaymentPlanInfo.setPrincipalAmount(remainAmount);//本金
                } else {
                    repaymentPlanInfo.setPrincipalAmount(BigDecimal.ZERO);//本金
                }
            }
            repaymentPlanInfo.setInterestAmount(interest);//利息

            //计算每月费用
            BigDecimal calcAmnt = BigDecimal.ZERO;
            if (TransBizConstant.FEE_CALC_METHOD_1.equals(installmentFee.getChargeBasis())) {//按贷款本金计算
                calcAmnt = loanAmount;
            } else if (TransBizConstant.FEE_CALC_METHOD_2.equals(installmentFee.getChargeBasis())) {//按每期本金计算
                calcAmnt = repaymentPlanInfo.getPrincipalAmount();
            } else if (TransBizConstant.FEE_CALC_METHOD_0.equals(installmentFee.getChargeBasis())) {//按剩余本金计算
                calcAmnt = remainAmount;
            }
            monthlyFee = this.getFeeAmnt(i, calcAmnt, installmentFee, totalTenor, dcimPlace, carryCondInt);

            repaymentPlanInfo.setFeeAmount(monthlyFee);//费用
            repaymentPlanInfo.setTotalAmount(repaymentPlanInfo.getFeeAmount().add(repaymentPlanInfo.getInterestAmount().add(repaymentPlanInfo.getPrincipalAmount())).setScale(dcimPlace, carryCondInt));//月还款总额
            repaymentPlanInfo.setBillingTenor(i);//期数

            //本次计划执行完成之后，进行剩余本金计算，剩余期数利息计算，每隔12个月重新计算剩余本金和利息
            if ((i + calTenor) % CommonContants.MONTH == 0) {
                remainAmount = remainAmount.subtract(yearPrincial);
                monthIntr = remainAmount.multiply(monthRate).setScale(dcimPlace, carryCondInt);
            }
            repaymentPlanInfo.setPaymentDueDate(thisPaymentDueDate);//还款日期
            repaymentPlanInfo.setIntrStartDate(lastPaymentDueDate);//起息日

            list.add(repaymentPlanInfo);
        }
        return list;
    }

    /**
     * 利随本清，随借随还公用还款计划
     *
     * @param loanMainInvestorInfo 贷款信息
     * @param dcimPlace            小数位数
     * @param carryCondInt         进位条件
     * @param firstPaymentDate     首次还款日
     * @param dailyRate            日利率
     * @param installmentFee       手续费参数
     * @return List<AmRepaymentPlanInvestorInfo> 还款计划
     * @throws TxnBizException
     * @author liuyang
     * @date 2020-05-19
     */
    private List<AmRepaymentPlanInvestorInfo> computeInterestPayEnd(AmLoanMainInvestorInfo loanMainInvestorInfo, Integer dcimPlace, Integer carryCondInt, Date firstPaymentDate, BigDecimal dailyRate, PrInvestorInstallmentFee installmentFee) throws TxnBizException {

        //贷款时间
        Date txnPostingDate = loanMainInvestorInfo.getPostingDate();

        //利息计算
        int diffDays = DateUtils.getDaysBetween(txnPostingDate, firstPaymentDate);
        BigDecimal interest = loanMainInvestorInfo.getLoanAmount().multiply(dailyRate).multiply(BigDecimal.valueOf(diffDays)).setScale(dcimPlace, carryCondInt);

        //费用计算
        BigDecimal fee = this.getFeeAmnt(TransBizConstant.ONE, loanMainInvestorInfo.getLoanAmount(), installmentFee, TransBizConstant.ONE, dcimPlace, carryCondInt);

        // 构建还款计划
        List<AmRepaymentPlanInvestorInfo> list = new ArrayList<>();
        AmRepaymentPlanInvestorInfo repaymentPlanInfo = buildRepaymentPlanInvestorInfo(loanMainInvestorInfo);
        repaymentPlanInfo.setPrincipalAmount(loanMainInvestorInfo.getLoanAmount());//本金
        repaymentPlanInfo.setInterestAmount(interest);//利息
        repaymentPlanInfo.setFeeAmount(fee);//费用
        repaymentPlanInfo.setTotalAmount(repaymentPlanInfo.getPrincipalAmount().add(interest).add(fee).setScale(dcimPlace, carryCondInt));//总金额
        repaymentPlanInfo.setBillingTenor(1);//期数
        repaymentPlanInfo.setIntrStartDate(txnPostingDate); //起息日
        repaymentPlanInfo.setPaymentDueDate(firstPaymentDate);//还款日

        list.add(repaymentPlanInfo);

        return list;
    }

    /**
     * 构建还款计划
     *
     * @param loanMainInvestorInfo 贷款信息
     * @return AmRepaymentPlanInvestorInfo 还款计划
     * @throws TxnBizException
     * @author liuyang
     * @date 2020-05-19
     */
    private AmRepaymentPlanInvestorInfo buildRepaymentPlanInvestorInfo(AmLoanMainInvestorInfo loanMainInvestorInfo) throws TxnBizException {
        //构建还款计划
        AmRepaymentPlanInvestorInfo repaymentPlanInvestorInfo = new AmRepaymentPlanInvestorInfo();
        repaymentPlanInvestorInfo.setRepayPrincipal(BigDecimal.ZERO);//当期已还本金
        repaymentPlanInvestorInfo.setRepayInterest(BigDecimal.ZERO);//当期已还利息
        repaymentPlanInvestorInfo.setRepayFee(BigDecimal.ZERO);//当期已还费用
        repaymentPlanInvestorInfo.setRepayPenaltyInterest(BigDecimal.ZERO);//当期已还罚息
        repaymentPlanInvestorInfo.setRepayCompoundInterest(BigDecimal.ZERO);//当前已还复利
        repaymentPlanInvestorInfo.setWaivedAmount(BigDecimal.ZERO);//当期免除金额
        repaymentPlanInvestorInfo.setRecordVersionNumber(TransBizConstant.INIT_RECORD_VERSION_NUMBER);//版本号
        repaymentPlanInvestorInfo.setCompensateFlag(TransBizConstant.NO_FLAG);//代偿标识
        repaymentPlanInvestorInfo.setBalTransFlag(TransBizConstant.NO_FLAG);//债权转移标识
        repaymentPlanInvestorInfo.setPlanStatus(TransBizConstant.LOAN_PLAN_STATUS_0);//状态 0 未抛账
        repaymentPlanInvestorInfo.setDelqDays(TransBizConstant.ZERO);//延滞天数
        try {
            repaymentPlanInvestorInfo.setId(segmentService.getId(TransBizConstant.AM_REPAYMENT_PLAN_INVERSTOR_INFO));
        } catch (SegmentException e) {
            logger.error("主键生成错误", e);
            throw new TxnBizException(TxnRespCode.ERROR.getUserTips());
        }
        repaymentPlanInvestorInfo.setInvestorLoanId(loanMainInvestorInfo.getInvestorLoanId());//出资方贷款订单id
        repaymentPlanInvestorInfo.setInvestorId(loanMainInvestorInfo.getInvestorId());//出资方ID
        repaymentPlanInvestorInfo.setLoanId(loanMainInvestorInfo.getLoanId());//订单id
        repaymentPlanInvestorInfo.setCustomerId(loanMainInvestorInfo.getCustomerId());//客户id
        repaymentPlanInvestorInfo.setTenantId(loanMainInvestorInfo.getTenantId());//租户id
        repaymentPlanInvestorInfo.setOrganizationId(loanMainInvestorInfo.getOrganizationId());//机构id
        repaymentPlanInvestorInfo.setChannel(loanMainInvestorInfo.getChannel());//渠道
        repaymentPlanInvestorInfo.setProductId(loanMainInvestorInfo.getProductId());//产品id
        repaymentPlanInvestorInfo.setUpdateBy(loanMainInvestorInfo.getUpdateBy());//最后更新操作员
        repaymentPlanInvestorInfo.setRecordVersionNumber(TransBizConstant.INIT_RECORD_VERSION_NUMBER);//记录版本号
        repaymentPlanInvestorInfo.setLiabilityFlag(TransBizConstant.LIABILITY_FLAG_03);//债务归属
        return repaymentPlanInvestorInfo;
    }

    /**
     * 计算首期还款日
     *
     * @param loanDate 业务日期
     * @param cycleDay 还款日
     * @param limitDay 间隔时间
     * @return Date首期还款日
     * @author liuyang
     * @date 2020-05-19
     */
    @Override
    public Date computeFirstPaymentDate(Date loanDate, Integer cycleDay, Integer limitDay) {
        GregorianCalendar gcEndDate = new GregorianCalendar();
        gcEndDate.setTime(loanDate);
        GregorianCalendar gcStartDate = new GregorianCalendar();
        gcStartDate.setTime(loanDate);
        //业务日期计算的号大于还款日
        if (gcStartDate.get(GregorianCalendar.DAY_OF_MONTH) > cycleDay) {
            GregorianCalendar gcTmpDate = new GregorianCalendar();
            gcTmpDate.setTime(loanDate);
            gcTmpDate.set(GregorianCalendar.DAY_OF_MONTH, cycleDay);
            //将临时还款日向后调整一个月
            gcTmpDate.add(GregorianCalendar.MONTH, 1);
            //计算当前时间到临时还款日时间差
            Integer diffDays = DateUtils.getDaysBetween(loanDate, gcTmpDate.getTime());
            //时间差大等于于间隔日期 实际还款日向后调整一个月
            if (diffDays >= limitDay) {
                gcEndDate.add(GregorianCalendar.MONTH, 1);
            } else {//时间差小于间隔日期 实际还款日向后调整俩个月  如月末特殊情况
                gcEndDate.add(GregorianCalendar.MONTH, 2);
            }
        } else {//业务日期计算的号小于还款日
            GregorianCalendar gcTmpDate = new GregorianCalendar();
            gcTmpDate.setTime(loanDate);
            gcTmpDate.set(GregorianCalendar.DAY_OF_MONTH, cycleDay);
            //当前时间到还款日时间差
            Integer diffDays = DateUtils.getDaysBetween(loanDate, gcTmpDate.getTime());
            //时间差小于间隔日期 还款日向后调整一个月
            if (diffDays < limitDay) {
                gcEndDate.add(GregorianCalendar.MONTH, 1);
            }
        }
        gcEndDate.set(GregorianCalendar.DAY_OF_MONTH, cycleDay);
        return gcEndDate.getTime();
    }

    /**
     * 获取本期费用金额
     *
     * @param billingTenor   第几期
     * @param calAmnt        每期费用
     * @param installmentFee 手续费参数信息
     * @param totalTenors    总期数
     * @param dcimPlace      小数位
     * @param carryCondInt   进位条件
     * @return BigDecimal 费用
     * @author liuyang
     * @date 2020-05-19
     */
    private BigDecimal getFeeAmnt(Integer billingTenor, BigDecimal calAmnt, PrInvestorInstallmentFee installmentFee, Integer totalTenors, Integer dcimPlace, Integer carryCondInt) {
        String feeType = installmentFee.getChargeFrequency();//费用收取频率
        BigDecimal feeRate = installmentFee.getChargePercent();////费率
        String chargeFixedOption = installmentFee.getChargeOption();//费用收取方式
        BigDecimal chargeMaxAmnt = installmentFee.getMaxAmount();//最大费用
        BigDecimal chargeMinAmnt = installmentFee.getMinAmount();//最小值

        BigDecimal feeAmt = BigDecimal.ZERO;
        if (calAmnt.compareTo(BigDecimal.ZERO) == 0) {
            return feeAmt;
        }

        if (TransBizConstant.FEE_CHARGE_FIXED_OPTION_NONE.equals(chargeFixedOption)) {
            if (TransBizConstant.FEE_CHARGE_OPTION_AVERAGE.equals(feeType)) {//每期平均收取
                feeAmt = calAmnt.multiply(feeRate);//每期金额
            } else if (TransBizConstant.FEE_CHARGE_OPTION_FIRST_ALL.equals(feeType)) {//首期全部收取（reserved）
                if (billingTenor == 1) {
                    feeAmt = calAmnt.multiply(feeRate);
                }
            } else if (TransBizConstant.FEE_CHARGE_OPTION_END_ALL.equals(feeType)) { //尾期全部收取
                if (billingTenor.equals(totalTenors)) {
                    feeAmt = calAmnt.multiply(feeRate);
                }
            }
            //与最大最小费用金额进行比较
            if (chargeMaxAmnt != null && feeAmt.compareTo(chargeMaxAmnt) > 0) {
                feeAmt = chargeMaxAmnt;
            } else if (chargeMinAmnt != null && feeAmt.compareTo(chargeMinAmnt) < 0) {
                feeAmt = chargeMinAmnt;
            }
        } else {
            if (TransBizConstant.FEE_CHARGE_OPTION_AVERAGE.equals(feeType)) {//每期平均收取
                feeAmt = installmentFee.getFixedAmount();//每期金额
            } else if (TransBizConstant.FEE_CHARGE_OPTION_FIRST_ALL.equals(feeType)) {//首期全部收取（reserved）
                if (billingTenor == 1) {
                    feeAmt = installmentFee.getFixedAmount();
                }
            } else if (TransBizConstant.FEE_CHARGE_OPTION_END_ALL.equals(feeType)) { //尾期全部收取
                if (billingTenor.equals(totalTenors)) {
                    feeAmt = installmentFee.getFixedAmount();
                }
            }
        }

        return feeAmt.setScale(dcimPlace, carryCondInt);
    }

    /**
     * 轧差资金方的本金计算
     *
     * @param repaymentPlanInfos         对客的订单还款计划
     * @param repaymentPlanInvestorInfos 非轧差资金方的还款计划
     * @return Map 轧差资金方的本金
     * @throws TxnBizException
     * @author liuyang
     * @date 2020-05-22
     */
    private Map<Integer, BigDecimal> getOffsetPrincialMap(List<AmRepaymentPlanInfo> repaymentPlanInfos, List<AmRepaymentPlanInvestorInfo> repaymentPlanInvestorInfos) throws TxnBizException {

        // 对客订单本金取得
        Map<Integer, BigDecimal> custPrincialMap = repaymentPlanInfos.stream().collect(Collectors.toMap(AmRepaymentPlanInfo::getBillingTenor, AmRepaymentPlanInfo::getPrincipalAmount));
        if (CollectionUtils.isEmpty(repaymentPlanInvestorInfos)) {
            // 如果出资方只有一个，则没有非轧差的出资方直接返回对客本金
            return custPrincialMap;
        }

        // 非轧差的出资方的本金汇
        Map<Integer, BigDecimal> investorPrincialMap = repaymentPlanInvestorInfos.stream().collect(Collectors.groupingBy(AmRepaymentPlanInvestorInfo::getBillingTenor, CollectorsUtils.summingBigDecimal(AmRepaymentPlanInvestorInfo::getPrincipalAmount)));

        // 轧差的出资方的本金计算
        Map<Integer, BigDecimal> offsetPrincialMap = new HashMap<>();
        for (Map.Entry<Integer, BigDecimal> entry : investorPrincialMap.entrySet()) {
            offsetPrincialMap.put(entry.getKey(), custPrincialMap.get(entry.getKey()).subtract(entry.getValue()));
        }
        return offsetPrincialMap;
    }

    /**
     * 根据贷款id和客户id查询所有出资方的还款计划
     *
     * @param investorLoanId 贷款id
     * @param customerId     客户id
     * @param tenantId       租户id
     * @param statusList     状态
     * @return List<AmRepaymentPlanInvestorInfo> 还款计划
     * @throws TxnBizException
     * @author liuyang
     * @date 2020-05-21
     */
    @Override
    public List<AmRepaymentPlanInvestorInfo> getPlanList(String investorLoanId, String customerId, String tenantId, List<String> statusList) throws TxnBizException {
        //查询订单还款计划
        List<AmRepaymentPlanInvestorInfo> planInfoList = extAmRepaymentPlanInvestorInfoMapper.selectByInvestorLoanIdAndStatus(investorLoanId, statusList, customerId, tenantId);
        //是否有还款计划
        if (CollectionUtils.isEmpty(planInfoList)) {
            logger.error("未查询到订单号：{}对应的还款计划", investorLoanId);
            throw new TxnBizException(TxnRespCode.NO_DATA.getCode(), TxnRespCode.NO_DATA.getUserTips());
        }
        return planInfoList;
    }

    /**
     * 轧差资金方的分期付款（等本等息）还款计划生成
     *
     * @param loanMainInvestorInfo       贷款信息
     * @param dcimPlace                  小数位数
     * @param carryCondInt               进位条件
     * @param firstPaymentDate           首次还款日
     * @param yearRate                   年利率
     * @param installmentFee             手续费参数
     * @param repaymentPlanInvestorInfos 非轧差资金方的还款计划
     * @param investorInterestRate       出资方利率
     * @param businessDate               业务日期
     * @return List<AmRepaymentPlanInvestorInfo> 还款计划
     * @throws TxnException
     * @author liuyang
     * @date 2020-05-22
     */
    private List<AmRepaymentPlanInvestorInfo> computeOffsetEPEI(AmLoanMainInvestorInfo loanMainInvestorInfo, int castTenor, Integer dcimPlace, Integer carryCondInt, Date firstPaymentDate, BigDecimal yearRate, PrInvestorInstallmentFee installmentFee, List<AmRepaymentPlanInvestorInfo> repaymentPlanInvestorInfos, PrInvestorInterestRate investorInterestRate, Date businessDate) throws TxnException {

        Integer totalTenor = loanMainInvestorInfo.getTotalTenor(); //总期数
        BigDecimal remainAmount = loanMainInvestorInfo.getLoanAmount(); //剩余本金
        BigDecimal princial = null; // 本金

        //月利率 公式: 年利率 * (1 + 期数) / (24 * 期数)   为准确计算  将除(24 * 期数) 放到了月利率计算公式中
        BigDecimal monthRate =
                yearRate.multiply(BigDecimal.valueOf(1L + totalTenor)).divide(BigDecimal.valueOf(24L * totalTenor), CommonContants.BIGDECIMAL_SCALE_7,
                        BigDecimal.ROUND_HALF_UP);

        //月利息 公式: 借款本金 * 月利率
        BigDecimal monthIntr = remainAmount.multiply(monthRate).setScale(dcimPlace, carryCondInt);

        //日利率
        int monthBase = investorInterestRateService.getMonthBase(investorInterestRate.getMonthBase(), businessDate);
        BigDecimal dailyRate = monthRate.divide(
                BigDecimal.valueOf(monthBase), CommonContants.BIGDECIMAL_SCALE_7, BigDecimal.ROUND_HALF_UP);

        // 轧差资金方的本金计算
        //查询出非取消还款计划，并过滤掉当前期之前的期次，避免提前还款后，对客还款计划关闭，导致资金方重新生成还款计划时无法找到对客还款计划的问题
        List<AmRepaymentPlanInfo> repaymentPlanInfos = repaymentPlanService.getPlanList(loanMainInvestorInfo.getLoanId(), loanMainInvestorInfo.getCustomerId(), loanMainInvestorInfo.getTenantId());
        repaymentPlanInfos = repaymentPlanInfos.stream().filter(plan -> plan.getBillingTenor() >= castTenor).collect(Collectors.toList());
        Map<Integer, BigDecimal> offsetPrincialMap = this.getOffsetPrincialMap(repaymentPlanInfos, repaymentPlanInvestorInfos);

        List<AmRepaymentPlanInvestorInfo> list = new ArrayList<>();
        for (int i = 1; i <= totalTenor; i++) {

            // 构建还款计划
            AmRepaymentPlanInvestorInfo repaymentPlanInfo = buildRepaymentPlanInvestorInfo(loanMainInvestorInfo);

            // 当期还款日
            Date thisPaymentDueDate = i == 1 ? firstPaymentDate : getCurrentPaymentDueDate(firstPaymentDate, i);

            // 上一期还款日（i是从1开始，所以需要减去2）
            Date lastPaymentDueDate = i == 1 ? businessDate : list.get(i - 2).getPaymentDueDate();

            // 利息本金计算
            BigDecimal interest = getMonthInterest(loanMainInvestorInfo, monthIntr, loanMainInvestorInfo.getLoanAmount(), i, firstPaymentDate, lastPaymentDueDate, thisPaymentDueDate, dailyRate, dcimPlace, carryCondInt);

            //本金扎差 最后一期本金按剩余本经计算
            if (i == totalTenor) {
                princial = remainAmount.setScale(dcimPlace, carryCondInt);
            } else {
                //获取实际期次与这次重新生成还款计划之间期次差
                int realTotalTenor = repaymentPlanInfos.stream().map(AmRepaymentPlanInfo::getBillingTenor).max(Integer::compareTo)
                        .orElseThrow(() -> new TxnException(TxnRespCode.BIZ_ERROR.getMsg()));
                int count = realTotalTenor - repaymentPlanInfos.size();
                princial = offsetPrincialMap.get(i + count);
            }

            //计算每月费用
            BigDecimal calcAmnt = BigDecimal.ZERO;
            //按贷款本金计算
            if (TransBizConstant.FEE_CALC_METHOD_1.equals(installmentFee.getChargeBasis())) {
                calcAmnt = loanMainInvestorInfo.getLoanAmount();
            }
            //按每期本金计算
            else if (TransBizConstant.FEE_CALC_METHOD_2.equals(installmentFee.getChargeBasis())) {
                calcAmnt = princial;
            }
            //按剩余本金计算
            else if (TransBizConstant.FEE_CALC_METHOD_0.equals(installmentFee.getChargeBasis())) {
                calcAmnt = remainAmount;
            }
            BigDecimal fee = this.getFeeAmnt(i, calcAmnt, installmentFee, totalTenor, dcimPlace, carryCondInt);

            repaymentPlanInfo.setFeeAmount(fee);//费用
            repaymentPlanInfo.setPrincipalAmount(princial);//本金
            repaymentPlanInfo.setInterestAmount(interest); //利息
            repaymentPlanInfo.setBillingTenor(i);//期数
            repaymentPlanInfo.setTotalAmount(princial.add(interest).add(fee).setScale(dcimPlace, carryCondInt));//还款总金额
            repaymentPlanInfo.setPaymentDueDate(thisPaymentDueDate);//还款日
            repaymentPlanInfo.setIntrStartDate(lastPaymentDueDate);//起息日

            //剩余金额重新计算
            remainAmount = remainAmount.subtract(princial);

            list.add(repaymentPlanInfo);
        }

        return list;
    }

    /**
     * 轧差资金方非分期的还款计划生成
     *
     * @param loanMainInvestorInfo       贷款信息
     * @param dcimPlace                  小数位数
     * @param carryCondInt               进位条件
     * @param firstPaymentDate           首次还款日
     * @param dailyRate                  日利率
     * @param monthRate                  月利率
     * @param installmentFee             手续费参数
     * @param businessDate               业务日期
     * @param repaymentPlanInvestorInfos 非轧差资金方的还款计划
     * @return List<AmRepaymentPlanInvestorInfo> 还款计划
     * @throws TxnBizException
     * @author liuyang
     * @date 2020-05-22
     */
    private List<AmRepaymentPlanInvestorInfo> computeOffsetOther(AmLoanMainInvestorInfo loanMainInvestorInfo, int castTenor, Integer dcimPlace, Integer carryCondInt, Date firstPaymentDate, BigDecimal dailyRate, BigDecimal monthRate, PrInvestorInstallmentFee installmentFee, Date businessDate, List<AmRepaymentPlanInvestorInfo> repaymentPlanInvestorInfos) throws TxnBizException {

        BigDecimal remainAmount = loanMainInvestorInfo.getLoanAmount(); //剩余本金
        Integer totalTenor = loanMainInvestorInfo.getTotalTenor(); //总期数
        BigDecimal monthlyFee = null;//每月费用
        BigDecimal interest = null;//利息
        BigDecimal princial = null;//本金

        // 轧差资金方的本金计算
        //查询出非取消还款计划，并过滤掉当前期之前的期次，避免提前还款后，对客还款计划关闭，导致资金方重新生成还款计划时无法找到对客还款计划的问题
        List<AmRepaymentPlanInfo> repaymentPlanInfos = repaymentPlanService.getPlanList(loanMainInvestorInfo.getLoanId(), loanMainInvestorInfo.getCustomerId(), loanMainInvestorInfo.getTenantId());
        repaymentPlanInfos = repaymentPlanInfos.stream().filter(plan -> plan.getBillingTenor() >= castTenor).collect(Collectors.toList());
        Map<Integer, BigDecimal> offsetPrincialMap = this.getOffsetPrincialMap(repaymentPlanInfos, repaymentPlanInvestorInfos);

        //循环期数 生成还款计划
        List<AmRepaymentPlanInvestorInfo> list = new ArrayList<>();
        for (int i = 1; i <= totalTenor; i++) {

            // 构建还款计划
            AmRepaymentPlanInvestorInfo repaymentPlanInfo = buildRepaymentPlanInvestorInfo(loanMainInvestorInfo);

            // 当期还款日
            Date thisPaymentDueDate = i == 1 ? firstPaymentDate : getCurrentPaymentDueDate(firstPaymentDate, i);

            // 上一期还款日（i是从1开始，所以需要减去2）
            Date lastPaymentDueDate = i == 1 ? businessDate : list.get(i - 2).getPaymentDueDate();

            //本金计算
            if (i == totalTenor) {
                // 最后一期（剩余贷款金额）
                princial = remainAmount.setScale(dcimPlace, carryCondInt);
            } else {
                //获取实际期次与这次重新生成还款计划之间期次差
                int realTotalTenor = repaymentPlanInfos.stream().map(AmRepaymentPlanInfo::getBillingTenor).max(Integer::compareTo)
                        .orElseThrow(() -> new RuntimeException(TxnRespCode.BIZ_ERROR.getMsg()));
                int count = realTotalTenor - repaymentPlanInfos.size();
                princial = offsetPrincialMap.get(i + count);
            }

            //计算每月费用
            BigDecimal calcAmnt = BigDecimal.ZERO;
            if (TransBizConstant.FEE_CALC_METHOD_1.equals(installmentFee.getChargeBasis())) {//按贷款本金
                calcAmnt = loanMainInvestorInfo.getLoanAmount();
            } else if (TransBizConstant.FEE_CALC_METHOD_2.equals(installmentFee.getChargeBasis())) {//按每期本金
                calcAmnt = princial;
            } else if (TransBizConstant.FEE_CALC_METHOD_0.equals(installmentFee.getChargeBasis())) {//按剩余本金
                calcAmnt = remainAmount;
            }
            monthlyFee = this.getFeeAmnt(i, calcAmnt, installmentFee, totalTenor, dcimPlace, carryCondInt);

            // 利息计算
            BigDecimal monthIntr = remainAmount.multiply(monthRate).setScale(dcimPlace, carryCondInt);
            interest = getMonthInterest(loanMainInvestorInfo, monthIntr, remainAmount, i, firstPaymentDate, lastPaymentDueDate, thisPaymentDueDate, dailyRate, dcimPlace, carryCondInt);

            //剩余本金刷新 减掉当期本金
            remainAmount = remainAmount.subtract(princial);

            repaymentPlanInfo.setPrincipalAmount(princial);//本金
            repaymentPlanInfo.setTotalAmount(princial.add(interest).add(monthlyFee).setScale(dcimPlace, carryCondInt));//还款总金额
            repaymentPlanInfo.setInterestAmount(interest);//利息
            repaymentPlanInfo.setFeeAmount(monthlyFee);//费用
            repaymentPlanInfo.setBillingTenor(i);//期数
            repaymentPlanInfo.setPaymentDueDate(thisPaymentDueDate);//还款日
            repaymentPlanInfo.setIntrStartDate(lastPaymentDueDate);//起息日

            list.add(repaymentPlanInfo);
        }
        return list;
    }



    @Override
    public InvestorLoanPlanParamBean buildLoanPlanParamBean(Date businessDate, Integer paymentDateDd, Integer intervalDays,
                                                            List<AmRepaymentPlanInvestorInfo> repaymentPlanInvestorInfos) {
        InvestorLoanPlanParamBean planParamBean = new InvestorLoanPlanParamBean();
        planParamBean.setBusinessDate(businessDate);
        planParamBean.setIntervalDays(intervalDays);
        planParamBean.setPaymentDateDd(paymentDateDd);
        planParamBean.setRepaymentPlanInvestorInfos(repaymentPlanInvestorInfos);
        return planParamBean;
    }

    /**
     * 第i期还款日计算
     *
     * @param firstPaymentDate 首期还款日
     * @param i                期数
     * @return Date 当期还款日
     */
    private Date getCurrentPaymentDueDate(Date firstPaymentDate, int i) {

        // 计算当期还款日
        Date thisPaymentDueDate = DateUtils.add(firstPaymentDate, GregorianCalendar.MONTH, i - 1);

        // 如果当期的还款日和首期还款日不同，则取当期的最后一天作为还款日
        if (!DateUtils.getDay(firstPaymentDate).equals(DateUtils.getDay(thisPaymentDueDate))) {
            thisPaymentDueDate = DateUtils.getMonthEndTime(thisPaymentDueDate);
        }

        return thisPaymentDueDate;
    }

    /**
     * 月利息计算
     *
     * @param loanMainInvestorInfo 订单信息
     * @param monthIntr            月利息
     * @param loanAmount           贷款本金
     * @param billingTenor         期数
     * @param firstPaymentDate     首期还款日
     * @param lastPaymentDueDate   上期还款日
     * @param thisPaymentDueDate   当期还款日
     * @param dailyRate            日利率
     * @param dcimPlace            保留小数位数
     * @param carryCondInt         进位条件
     * @return 月利息计算
     * @throws TxnBizException
     */
    private BigDecimal getMonthInterest(AmLoanMainInvestorInfo loanMainInvestorInfo, BigDecimal monthIntr, BigDecimal loanAmount, int billingTenor,
                                        Date firstPaymentDate, Date lastPaymentDueDate, Date thisPaymentDueDate, BigDecimal dailyRate, Integer dcimPlace, Integer carryCondInt) throws TxnBizException {

        // 获取产品信息
        PrProductTable productTable = productTableService.findProByProIdAndOrgId(loanMainInvestorInfo.getProductId(), loanMainInvestorInfo.getOrganizationId(), loanMainInvestorInfo.getTenantId());

        BigDecimal interestAmount;
        if (1 == billingTenor) {
            // 首期
            String firstOption = productTable.getFirstPaymentOption();
            // 获取放款日 与 首期还款日间隔天数
            int days = DateUtils.getDaysBetween(lastPaymentDueDate, firstPaymentDate);
            if (TransBizConstant.FIRST_PAYMENT_OPTION_01.equals(firstOption)) {
                // 日对日按月，否则按日
                if (DateUtils.getDay(firstPaymentDate).equals(DateUtils.getDay(lastPaymentDueDate))) {
                    interestAmount = monthIntr;
                } else {
                    interestAmount = loanAmount.multiply(dailyRate).multiply(BigDecimal.valueOf(days)).setScale(dcimPlace, carryCondInt);
                }
            } else if (TransBizConstant.FIRST_PAYMENT_OPTION_02.equals(firstOption)) {
                // 按日
                interestAmount = loanAmount.multiply(dailyRate).multiply(BigDecimal.valueOf(days)).setScale(dcimPlace, carryCondInt);
            } else {
                // 按月
                interestAmount = monthIntr;
            }
        } else {
            // 非首期
            String planOption = productTable.getPlanOption();
            // 获取放款日与首期还款日间隔天数
            int days = DateUtils.getDaysBetween(lastPaymentDueDate, thisPaymentDueDate);
            if (TransBizConstant.PLAN_OPTION_01.equals(planOption)) {
                // 日对日按月，否则按日
                if (DateUtils.getDay(firstPaymentDate).equals(DateUtils.getDay(thisPaymentDueDate))) {
                    interestAmount = monthIntr;
                } else {
                    interestAmount = loanAmount.multiply(dailyRate).multiply(BigDecimal.valueOf(days)).setScale(dcimPlace, carryCondInt);
                }
            } else if (TransBizConstant.PLAN_OPTION_02.equals(planOption)) {
                // 按日
                interestAmount = loanAmount.multiply(dailyRate).multiply(BigDecimal.valueOf(days)).setScale(dcimPlace, carryCondInt);
            } else {
                // 按月
                interestAmount = monthIntr;
            }
        }
        return interestAmount;
    }
}
